import bpy
import ast
from mathutils import Vector
from bpy.types import Context
from ....addon.naming import FluidLabNaming
from ....libs.functions.basics import set_active_object
from ....libs.functions.get_common_vars import get_common_vars


# -=| FLUID PROPS |=- 
# Para saber si las leo de .settings o de .settings.fluid:
physics_props = (
    "stiffness", 
    "linear_viscosity", 
    "repulsion", 
    "use_factor_repulsion",
    "stiff_viscosity",
    "fluid_radius", 
    "rest_density",
)
spring_props = (
    "spring_force",
    "use_viscoelastic_springs",
    "yield_ratio",
    "plasticity",
    "use_initial_rest_length",
    "spring_frames",
    "use_factor_rest_length",
    "rest_length"
)
fluid_props = physics_props + spring_props # <- lo usa particles_props_update() y ui_update()

weights_props = (
                    "collection",
                    "gravity",
                    "all",
                    "force",
                    "vortex",
                    "magnetic",
                    "harmonic",
                    "charge",
                    "lennardjones",
                    "wind",
                    "curve_guide",
                    "texture",
                    "smokeflow",
                    "turbulence",
                    "drag",
                    "boid",
)

#--------------------------------------------------------------
# Mi white list de properties <- usado en ui_update()
#--------------------------------------------------------------
white_list_properties = {
                            "emission":{
                                "global_mode_emission_sect": {
                                    "GEOMETRY": (
                                        "emit_from",
                                        "grid_resolution",
                                        "size",
                                        "size_random",
                                        "frame_start",
                                        "frame_end",
                                        "lifetime",
                                        "lifetime_random",
                                        "use_modifier_stack",
                                        "show_unborn",
                                        "grid_random",
                                    ),
                                    "INFLOW": (
                                        "emit_from",
                                        "count",
                                        "inflow_resolution",
                                        "size",
                                        "size_random",
                                        "frame_start",
                                        "frame_end",
                                        "lifetime",
                                        "lifetime_random",
                                        "use_modifier_stack",
                                        "show_unborn",
                                        "use_emit_random",
                                        "use_even_distribution",
                                        "count_or_resolution",
                                    )
                                },
                                "global_mode_velocity_sect": {
                                    "GEOMETRY": (
                                        "normal_factor",
                                        "object_factor",
                                        "object_align_factor_x",
                                        "object_align_factor_y",
                                        "object_align_factor_z",
                                    ),
                                    "INFLOW": (
                                        "normal_factor",
                                        "object_factor",
                                        "object_align_factor_x",
                                        "object_align_factor_y",
                                        "object_align_factor_z",
                                    )
                                },
                                "global_mode_viewport_display_sect": {
                                    "GEOMETRY": (
                                        "display_color",
                                    ),
                                    "INFLOW": (
                                        "display_color",
                                    )
                                }
                            },
                            "physics":{
                                "global_mode_integration_sect": { 
                                    "GEOMETRY": (
                                        "timestep",
                                        "subframes",
                                        "use_adaptive_subframes",
                                        "courant_target",
                                        "collision_coll",
                                    ),
                                    "INFLOW": (
                                        "timestep",
                                        "subframes",
                                        "use_adaptive_subframes",
                                        "courant_target",
                                        "collision_coll",
                                    )
                                },
                                "global_mode_physics_sect": {
                                    "GEOMETRY": (
                                        "mass",
                                        "use_multiply_size_mass",
                                        "stiffness",
                                        "linear_viscosity",
                                        "drag_factor",
                                        "damping",
                                        "use_size_deflect",
                                    ),
                                    "INFLOW": (
                                        "mass",
                                        "use_multiply_size_mass",
                                        "stiffness",
                                        "linear_viscosity",
                                        "drag_factor",
                                        "damping",
                                        "use_size_deflect",
                                    )
                                },
                                "global_mode_advanced_b_sect": {
                                    "GEOMETRY": (
                                        "repulsion",
                                        "stiff_viscosity",
                                        "fluid_radius",
                                        "rest_density",
                                    ),
                                    "INFLOW": (
                                        "repulsion",
                                        "stiff_viscosity",
                                        "fluid_radius",
                                        "rest_density",
                                    )
                                },
                                "global_mode_advanced_field_weights_sect":{
                                    "GEOMETRY": weights_props,
                                    "INFLOW": weights_props
                                },
                            },
                            "springs":{
                                "global_mode_springs_sect": {
                                    "GEOMETRY": spring_props,
                                    "INFLOW": spring_props
                                }
                            }
                        }


def get_container(psys, prop):
    """ El container puede ser el psys.settings o el psys.settings.fluid o el psys.settings.effector_weights """
    if prop in fluid_props:
        return psys.settings.fluid
    
    elif prop in weights_props:
        return psys.settings.effector_weights

    else:
        return psys.settings
    

def frame_start_frame_end_relations(self, call_from:str, new_value:float) -> None:
    ######################################################               
    """ El frame_start afecta al frame_end y viceversa """
    ######################################################

    if call_from == 'frame_start':
        if new_value > getattr(self, "frame_end"):
            self.frame_end = new_value

    if call_from == 'frame_end':
        if new_value < getattr(self, "frame_start"):
            self.frame_start = new_value


def particles_props_update(self, context:Context, prop_section:str='', call_from:str=None) -> None:

    # print(f"---- Particles props update [{call_from}] ----")

    """ Particles Properties Update """

    scn, fluid_groups = get_common_vars(context, get_scn=True, get_fluid_groups=True)
    active_group = fluid_groups.active

    # Cada vez que cambiamos cualquier property rebobinamos al inicio:
    if scn.frame_current != scn.frame_start:
        scn.frame_set(scn.frame_start)

    prev_ac_ob = context.active_object
    
    # ##################################################################################################
    # Si el active item esta en usar como local, solo se trabaja con el:

    active_emitter_item = active_group.emitters.active
    if active_emitter_item is None:
        return

    # print("prop_section", prop_section)
    global_mode = getattr(active_emitter_item.switchers, prop_section)
    
    # print(f"global_mode {global_mode}")

    if global_mode:
        # Es Global:
        all_emitters_items =  [emitter_item for emitter_item in active_group.emitters.get_all_items if getattr(emitter_item.switchers, prop_section)]
    else:
        # Es local:
        all_emitters_items = [active_emitter_item]

    # print(f"all_emitters_items {[item.emitter for item in all_emitters_items]}")
    # ##################################################################################################

    for item in all_emitters_items:

        emitter_ob = next((ob for ob in item.group_coll.objects if ob.fluidlab.id_name == "Emitter_"+ item.id_name ), None)
        if not emitter_ob:
            continue

        # Particle System Active:
        psys = emitter_ob.particle_systems.active
        
        if not psys:
            continue
    
        if call_from == "prev_count":
            continue
        
        container = get_container(psys, call_from)
        
        # La excepción para el inflow_resolution (poder decirle x numero de particulas por cara por cada frame):
        if call_from == "inflow_resolution":

            # Porque en modo GEOMETRY, container('ParticleSettings') object has no attribute 'inflow_resolution':
            if active_group.emitter_type == 'GEOMETRY':
                continue
            
            elif active_group.emitter_type == 'INFLOW':

                # solo si estamos en modo RESOLUTION:
                if item.emission.count_or_resolution == 'COUNT':
                    continue

                if container.distribution != 'JIT':
                    container.distribution = 'JIT'
                
                inflow_resolution = getattr(self, call_from)
                container.userjit = inflow_resolution

                total_of_frames = container.frame_end - container.frame_start 
                
                current_value = getattr(container, "count")
                new_value = int(inflow_resolution * total_of_frames)

                if new_value != current_value:
                    setattr(item.emission, "prev_count", current_value)
                    setattr(container, "count", new_value)
                
                if item.emission.inflow_resolution != inflow_resolution:
                    setattr(item.emission, "inflow_resolution", inflow_resolution)

                continue

        elif call_from == "count":

            # sólo si el nuevo valor es distinto:
            if container.count != self.count:
            
                # Guardamos cada vez q cambia el size el usuario como prev_count:
                self.prev_count = self.count
                
                container.count = self.count            

            # Continuamos a la siguiente emitter item:
            continue

        
        # La excepción para el grid_resolution:
        elif call_from == "grid_resolution":
            grid_resolution = self.grid_resolution

            # Si no tiene la escala aplicada, se la aplicamos:
            if emitter_ob.scale != Vector((1.0, 1.0, 1.0)):
                bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)

            set_active_object(context, emitter_ob)
            
            ps_mod = emitter_ob.modifiers.get(psys.name)

            if not ps_mod:
                continue

            # Desactivamos las particulas para q no explote:
            prev_mod_status = ps_mod.show_viewport 
            ps_mod.show_viewport = False

            container.grid_resolution = grid_resolution

            particle_size = max(emitter_ob.dimensions)/grid_resolution/2
            container.particle_size = particle_size
            container.display_size = particle_size

            # Reseteamos la cache:            
            # Fuerzo el refresco de la cache:
            scn.frame_set(scn.frame_start+1)
            scn.frame_set(scn.frame_start)

            # volvemos a restaurar el estado del modifier de las particulas:
            ps_mod.show_viewport = prev_mod_status

            # Ya hemos terminado con este grid_resolution pasamos al siguiente:
            continue


        # La excepción para el collision collection:
        elif call_from == "collision_coll":

            new_value = getattr(active_emitter_item.physics, call_from)
            previous_value = getattr(item.physics, call_from)

            if new_value != previous_value:
                setattr(item.physics, call_from, new_value)

            # Si en el sistema de particulas lo tiene distinto se lo seteamos:            
            real_prev_value = getattr(container, "collision_collection")
            if real_prev_value != new_value:
                setattr(container, "collision_collection", new_value)

            # Continiamos al siguiente emitter item:
            continue


        # La excepción para show_unborn
        if call_from == "show_unborn":
            vertex_ob = item.vertex_ob
            if vertex_ob:
                p_instance_mod = vertex_ob.modifiers.get(FluidLabNaming.PARTICLE_INSTANCE_ALIVE_MOD)
                if p_instance_mod:
                    p_instance_mod.show_unborn = getattr(self, call_from)

        # La excepción para el size:
        sizes = ("particle_size", "display_size")
        if call_from in sizes:
            
            # sólo si el nuevo valor es distinto:
            if container.particle_size != self.size:
                container.particle_size = self.size
            
            # sólo si el nuevo valor es distinto:
            if container.display_size != self.size:
                container.display_size = self.size
            
            # Guardamos cada vez q cambia el size el usuario como prev_size:
            # print(f"item: {item.label_txt} Guardamos el previous size: {self.size}")
            self.prev_size = self.size
            item.emission.prev_size = self.size

            # Continuamos a la siguiente emitter item:
            continue

        

        # La excepción para los object_align_factor_*:
        current_value = getattr(container, call_from[:-2]) if call_from.startswith("object_align_factor_") else getattr(container, call_from)
        
        if call_from == "object_align_factor_x":
            new_value = [getattr(self, call_from), current_value[1], current_value[2]]
        
        elif call_from == "object_align_factor_y":
            new_value = [current_value[0], getattr(self, call_from), current_value[2]]
        
        elif call_from == "object_align_factor_z":
            new_value = [current_value[0], current_value[1], getattr(self, call_from)]

        else:

            # La norma general:
            new_value = getattr(self, call_from)
    
        # sólo si el nuevo valor es distinto:
        if current_value != new_value:

            if call_from == "object_align_factor_x" or call_from == "object_align_factor_y" or call_from == "object_align_factor_z":  
                setattr(container, call_from[:-2], new_value)
            else:
                
                # La norma general:
                setattr(container, call_from, new_value)

        # Relaciones del frame start y end de blender:
        # Si el frame start es mayor que el end, el end sube.
        # Si el frame end es menor que el start el start baja.
        if call_from in ("frame_start", "frame_end"):
            frame_start_frame_end_relations(self, call_from, new_value)
        
        # excepción inflow_resolution:
            if self.count_or_resolution == 'RESOLUTION':
                new_value = getattr(self, "inflow_resolution")
                setattr(self, "inflow_resolution", new_value)
            
        # excepción inflow_resolution:
        if call_from == "count":
            psys.settings.userjit = 0

    
    if prev_ac_ob:
        set_active_object(context, prev_ac_ob)


def sync_curr_prop_after_click_in_chain(active_group, active_emitter_item, prop_section:str, prop_name:str) -> None:

    """ 
        Estamos actualizando la ui, si es GLOBAL se asegura de que todas las propiedades globales esten igualadas.
        Si es LOCAL no se actualiza nada.
    """

    # relacionamos para obtener las properties en funcion de su section id:
    sections = {
        "global_mode_emission_sect": "emission",
        "global_mode_physics_sect": "physics"
    }

    # Obtenemos si estamos en local o global en la seccion actual:
    global_mode = getattr(active_emitter_item.switchers, prop_section)
    if global_mode:
        # Estamos en Global: 
        all_emitters_items =  [emitter_item for emitter_item in active_group.emitters.get_all_items if getattr(emitter_item.switchers, prop_section)]
    else:
        # Estamos en local: el from_prop y el to_prop serán iguales y no se hará nada más.
        all_emitters_items = [active_emitter_item]
    
    # Sync la propiedad prop_name tras darle al icono de las cadenas:
    for emitter_item in all_emitters_items:
        
        from_prop = getattr( getattr(emitter_item, sections[prop_section]), prop_name )
        to_prop = getattr( getattr(active_emitter_item, sections[prop_section]), prop_name )
        
        # Si ya es igual el valor pasamos al siguiente item:
        if to_prop == from_prop:
            continue
        
        # igualamos las propiedades que tengan distintos valores:
        setattr( getattr(active_emitter_item, sections[prop_section]), prop_name, from_prop )


def ui_update(context:Context, active_emitter_item, without_self_ob:bool=False, subsection:str='CURRENT') -> None:
    
    # Tras clickear en las cadenas invocamos esto

    # print("Execute UI UPDATE")

    ######################################################################################################
    """ Cargar los datos de ui en funcion de si es GLOBAL o LOCAL """
    ######################################################################################################
    # Nota, si un valor en la ui es diferente del valor real del objeto, este será seteado al valor de ui.

    fluidlab, fluid_groups = get_common_vars(context, get_fluidlab=True, get_fluid_groups=True)

    active_emitter_item = None

    active_group = fluid_groups.active
    if active_group:
        emitters_list = active_group.emitters
    
        active_emitter_item = emitters_list.active    

    if not active_emitter_item:
        return None

    # En que subseccion de properties vamos a buscar: (emission, physics, springs, etc..., "en la sección en la que esté el usuario", pero ahora 
    # puede ser en la que se quiera a traves del argumento "subsection"):
    target_props = fluidlab.fluid_settings.fs_sections.lower() if subsection == 'CURRENT' else subsection.lower()
    
    # Fix, La subsección de Fluid Settings > Animation, no tiene properties que actualizar con esta function de ui update:
    if target_props == "animation":
        return
    
    # Obtendo los custom properties names (active_emitter_item.emission.x, active_emitter_item.physics.x, etc...):
    cp_names = getattr(active_emitter_item, target_props)

    #---------------------------------------------------------------------------------
    # Seteando las properties:
    #---------------------------------------------------------------------------------
    
    def convert_string_to_python_type(input_string):
        try:
            # Intenta evaluar la cadena utilizando ast.literal_eval
            result = ast.literal_eval(input_string)
        except (ValueError, SyntaxError):
            # Si no se puede evaluar, simplemente devuelve la cadena original
            result = input_string
        return result

    # Por cada seccion leemos las propiedades correspondientes:
    for section, props in white_list_properties[target_props].items(): # <- white_list_properties[ "emission" | "physics" | etc... ]

        global_mode = getattr(active_emitter_item.switchers, section)
        emitter_ob = emitters_list.get_current_emitter
        
        ob = fluid_groups.get_first_global_emitter(section_props=section, without_self_ob=without_self_ob, target="object") if global_mode else emitter_ob
        if ob is None:
            continue
        
        # print(f"Emitter: {ob.name}, global_mode {global_mode}")
        for prop in props[active_group.emitter_type]: # <- props[ "GEOMETRY" | "INFLOW" ]

            psys = ob.particle_systems.get(FluidLabNaming.PS_NAME)
            if not psys:
                continue
                
            container = get_container(psys, prop)

            # Casos especiales:
            if prop.startswith("object_align_factor_"):

                relations = { "object_align_factor_x": 0, "object_align_factor_y": 1, "object_align_factor_z": 2 }
                val_obt = container.object_align_factor[relations[prop]]
            
            # Excepción inflow_resolution:
            elif prop.startswith("inflow_resolution"):
                # emitter_item = fluid_groups.get_first_global_emitter(section_props=section, without_self_ob=without_self_ob, target="item")
                # val_obt = emitter_item.emission.inflow_resolution
                val_obt = active_emitter_item.emission.inflow_resolution

            
            elif prop == "count_or_resolution":

                sync_curr_prop_after_click_in_chain(active_group, active_emitter_item, prop_section="global_mode_emission_sect", prop_name="count_or_resolution")
                continue

            elif prop == "collision_coll":

                sync_curr_prop_after_click_in_chain(active_group, active_emitter_item, prop_section="global_mode_physics_sect", prop_name="collision_coll")
                continue

            else:
                
                # El property size es un caso especial, por eso uso en getattr: ("particle_size" if tname == "size" else tname)
                # los settings de las particulas no tienen atributo .size, tienen particle_size y fisplay_size mi .size actualiza ambos.
                val_obt = convert_string_to_python_type(getattr(container, "particle_size" if prop == "size" else prop))
                # print(tname, val_obt) # <-- para obtener el whitelist con el filtrado

            if prop == "size":

                # Si es de tipo geometry y no estamos especificando por size skypeamos:
                if active_group.emitter_type == 'GEOMETRY':
                    if not active_emitter_item.gr_sz_toggle:
                        continue

                prev_size = getattr(cp_names, "prev_size") 
                if prev_size >= 0:
                    # print("prev_size", prev_size)
                    val_obt = prev_size

            # if prop == "lifetime":
            #     print(f"Recuperando ui de {ob.name}, seteando {prop} : {val_obt}")
            
            # print(f"cp_names {cp_names}, prop {prop}, val_obt {val_obt}")
            setattr(cp_names, prop, val_obt)